{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a href='http://www.holoviews.org'><img src=\"../../assets/hv+bk.png\" alt=\"HV+BK logos\" width=\"40%;\" align=\"left\"/></a>\n",
    "<div style=\"float:right;\"><h2>Exercise 5: Exporting and Deploying Apps</h2></div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "from itertools import cycle\n",
    "\n",
    "import holoviews as hv \n",
    "import geoviews as gv\n",
    "import param\n",
    "import parambokeh\n",
    "import dask.dataframe as dd\n",
    "import pandas as pd\n",
    "\n",
    "from colorcet import cm_n\n",
    "from bokeh.document import Document\n",
    "from holoviews.operation.datashader import datashade\n",
    "from holoviews.streams import RangeXY\n",
    "\n",
    "hv.extension('bokeh')\n",
    "\n",
    "def taxi_trips_stream(source='../../data/nyc_taxi_wide.parq', frequency='H'):\n",
    "    \"\"\"Generate dataframes grouped by given frequency\"\"\"\n",
    "    def get_group(resampler, key):\n",
    "        try:\n",
    "            df = resampler.get_group(key)\n",
    "            df.reset_index(drop=True)\n",
    "        except KeyError:\n",
    "            df = pd.DataFrame()\n",
    "        return df\n",
    "\n",
    "    df = pd.read_parquet(source,\n",
    "                     columns=['tpep_pickup_datetime', 'pickup_x', 'pickup_y', 'tip_amount'])\n",
    "    df = df.set_index('tpep_pickup_datetime', drop=True)\n",
    "    df = df.sort_index()\n",
    "    r = df.resample(frequency)\n",
    "    chunks = [get_group(r, g) for g in sorted(r.groups)]\n",
    "    indices = cycle(range(len(chunks)))\n",
    "    while True:\n",
    "        yield chunks[next(indices)]\n",
    "\n",
    "trips = taxi_trips_stream()\n",
    "example = next(trips)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 1\n",
    "\n",
    "In this example we will be doing something a bit more complex - building a full app with widgets. Below we have already outlined the basic components of this app, including a ``NYCTaxiStream`` class that takes some ``data`` along with the ``x_range`` and ``y_range`` and turns those into a set of ``datashade``d ``Points``. You should execute that cell before you change anything about it, so that you are sure it works at the start.\n",
    "\n",
    "Your task will be to add some custom widgets to this plot by adding parameters to the ``NYCTaxiStream`` class to control the NYCTaxiStream output. \n",
    "\n",
    "1.) Begin by adding a widget that will allow us to select between the different cmaps available from ``cm_n.values()``.\n",
    "\n",
    "<b><a href=\"#hint1\" data-toggle=\"collapse\">Hint</a></b>\n",
    "\n",
    "<div id=\"hint1\" class=\"collapse\">\n",
    "Adding a widget is as simple as adding a Parameter to the class. To select between different options use:\n",
    "<br><br>\n",
    "<code>\n",
    "    param.ObjectSelector(default=..., objects=...) \n",
    "</code>\n",
    "</div>\n",
    "\n",
    "<b><a href=\"#hint2\" data-toggle=\"collapse\">Hint</a></b>\n",
    "\n",
    "<div id=\"hint2\" class=\"collapse\">\n",
    "The ``datashade`` operation accepts a ``cmap`` argument.\n",
    "</div>\n",
    "\n",
    "2.) Now add a parameter that filters the data based on one of the columns in the data, e.g. trips with >N passengers.\n",
    "\n",
    "\n",
    "<b><a href=\"#hint3\" data-toggle=\"collapse\">Hint</a></b>\n",
    "\n",
    "<div id=\"hint3\" class=\"collapse\">\n",
    "The ``param.Number`` and ``param.NumericTuple`` parameters allow defining scalar values and ranges respectively. Ensure that you set the ``bounds`` which declare the range of valid values.\n",
    "<br><br>\n",
    "<code>\n",
    "    param.ObjectSelector(default=..., objects=...) \n",
    "</code>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class NYCTaxiStream(hv.streams.Stream):\n",
    "\n",
    "    def make_view(self, data, x_range, y_range, **kwargs):\n",
    "        points = hv.Points(data, ['pickup_x', 'pickup_y'])\n",
    "        return datashade(points, x_range=x_range, y_range=y_range,\n",
    "                         dynamic=False)\n",
    "\n",
    "tile_options = dict(width=600,height=400,xaxis=None,yaxis=None,bgcolor='black',show_grid=False)\n",
    "tiles = gv.WMTS('https://maps.wikimedia.org/osm-intl/{Z}/{X}/{Y}@2x.png').opts(plot=dict(tile_options))\n",
    "\n",
    "nyc_stream = NYCTaxiStream()\n",
    "buffer = hv.streams.Buffer(example, length=1000000)\n",
    "obj = tiles * hv.DynamicMap(nyc_stream.make_view, streams=[nyc_stream, RangeXY(), buffer])\n",
    "\n",
    "plot = hv.renderer('bokeh').get_plot(obj, Document())\n",
    "parambokeh.Widgets(nyc_stream, view_position='right', callback=nyc_stream.event, plots=[plot])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To test the app you can ``start`` and ``stop`` streaming updates by running the two cells below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from tornado.ioloop import PeriodicCallback\n",
    "\n",
    "def update():\n",
    "    buffer.send(next(trips))\n",
    "\n",
    "periodic = PeriodicCallback(update, 100)\n",
    "periodic.start()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "periodic.stop()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<b><a href=\"#solution1\" data-toggle=\"collapse\">Solution</a></b>\n",
    "\n",
    "<div id=\"solution1\" class=\"collapse\">\n",
    "<br>\n",
    "<code>class NYCTaxiStream(hv.streams.Stream):\n",
    "\n",
    "    colormap = param.ObjectSelector(default=cm_n[\"fire\"], objects=cm_n.values())\n",
    "\n",
    "    minimum_tip = param.Number(default=0, bounds=(0, 20))\n",
    "    \n",
    "    def make_view(self, data, x_range, y_range, **kwargs):\n",
    "        points = hv.Points(data, ['pickup_x', 'pickup_y'])\n",
    "        if self.minimum_tip:\n",
    "            points = points.select(tip_amount=(self.minimum_tip, None))\n",
    "        return datashade(points, x_range=x_range, y_range=y_range,\n",
    "                         dynamic=False, cmap=self.colormap)\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Take the app you have written move it to a ``.py`` file and adapt it so it can be deployed using bokeh server."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
